/**
 * 
 */
package edu.sjtu.se.prophet.cluster;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;

/**
 * @author willard
 * 
 */
public abstract class BaseMedoids implements Clusterer {
    private static Logger logger = Logger.getLogger(BaseMedoids.class);

    protected Collection<Point> points;

    protected int numberOfClusters;

    protected DistanceMeter meter;

    protected Set<Point> medoids;

    protected List<Cluster> clusters;

    protected abstract Collection<Cluster> cluster();

    /*
     * (non-Javadoc)
     * 
     * @see edu.sjtu.se.prophet.cluster.Clusterer#cluster(java.util.Collection,
     * edu.sjtu.se.prophet.cluster.DistanceMeter, int)
     */
    @Override
    public Collection<Cluster> cluster(Collection<Point> points,
            DistanceMeter meter, int numberOfClusters)
            throws InvalidClusterNumberException {

        if (numberOfClusters <= 0 || numberOfClusters > points.size())
            throw new InvalidClusterNumberException("numberOfClusters="
                    + numberOfClusters + ", number of points=" + points.size());

        this.points = points;
        this.meter = meter;
        this.numberOfClusters = numberOfClusters;

        logger.info("Number of points: " + points.size()
                + ", number of clusters: " + numberOfClusters);

        return cluster();
    }

    /**
     * @return
     */
    protected int getCost() {
        int cost = 0;
        for (Point medoid : medoids) {
            cost += getCost(medoid);
        }
        return cost;
    }

    protected int getCost(Point medoid) {
        int cost = 0;
        for (Point point : medoid.getCluster().getPoints()) {
            cost += meter.distance(medoid, point);
        }
        return cost;
    }

    /**
     * Associate each data point to the closest medoid.
     */
    protected void associateDataPoints() {

        for (Point point : points) {
            if (!medoids.contains(point)) {
                int distance = Integer.MAX_VALUE;
                Point medoid = null;
                for (Point m : medoids) {
                    int d = meter.distance(point, m);
                    if (d < distance) {
                        distance = d;
                        medoid = m;
                    }
                }
                // // current data point is far from all medoids, then pick a
                // // random medoid.
                // if (medoid == null) {
                // int size = medoids.size();
                // int item = new Random().nextInt(size);
                // int i = 0;
                // for (Point p : medoids) {
                // if (i == item) {
                // medoid = p;
                // break;
                // }
                // i = i + 1;
                // }
                // }

                if (medoid != null)
                    point.moveTo(medoid.getCluster());

            }
        }

    }
}
